使用 SQLite 数据库
Dora SSR 引擎为开发者提供了 SQLite 数据库的集成,用于管理和查询大量游戏数据,以及进行关键游戏数据的持久化存储。本教程将带您从零开始,逐步了解如何使用 Dora 的数据库功能。
1. 引言
在游戏开发中,经常需要管理大量的游戏数据,如玩家信息. 游戏物品. 关卡配置等。使用数据库可以高效地存储和检索这些数据。Dora SSR 引擎集成了 SQLite 数据库,提供了一系列简单易用的接口,让您可以轻松地进行数据库操作。
1.1 初识数据库操作
在开始之前,确保您已了解以下概念:
- 数据库(Database):存储和管理数据的系统。
- 数据表(Table):数据库中的数据存储结构,由行和列组成。
- 行(Row):表中的一条记录。
- 列(Column):表中某个属性的集合。
2. DB 类简介
DB
类是 Dora 提供的用于数据库操作的核心类。通过这个类,您可以执行以下操作:
- 检查数据表是否存在
- 执行 SQL 查询
- 插入. 更新. 删除数据
- 执行事务
- 进行异步操作
2.1 DB 类相关重要概念
-
Column:表示数据库列的数据类型,可以是
integer
.number
.string
或boolean
。特别提示在 Dora SSR 中数据库列的 boolean 类型仅支持
false
值,用于表示数据库中的空值(NULL)。如果需要在数据库中存储布尔值,需要使用数值类型的0
和1
来代替false
和true
。 -
Row:表示数据库中的一行数据,通常是一个包含多个
Column
的 Lua 表。 -
SQL:表示 SQL 查询语句,可以是字符串或包含参数的字符串加上参数表。
3. 基本操作示例
下面, 我们将通过一些基本操作来熟悉 DB
类的使用。
3.1 检查数据表是否存在
在操作数据库之前,通常需要确认某个数据表是否存在。可以使用 exist
方法:
- Lua
- Teal
- TypeScript
- YueScript
local tableExists = DB:exist("test_table")
print(tableExists and "表存在" or "表不存在")
local tableExists = DB:exist("test_table")
print(tableExists and "表存在" or "表不存在")
const tableExists = DB.exist("test_table");
print(tableExists ? "表存在" : "表不存在");
tableExists = DB\exist "test_table"
print tableExists and "表存在" or "表不存在"
3.2 创建和删除数据表
可以使用 exec
方法执行创建和删除表的 SQL 语句:
- Lua
- Teal
- TypeScript
- YueScript
-- 删除名为 test_table 的数据表(如果存在)
DB:exec("DROP TABLE IF EXISTS test_table")
-- 创建名为 test_table 的数据表
DB:exec("CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)")
-- 删除名为 test_table 的数据表(如果存在)
DB.exec("DROP TABLE IF EXISTS test_table")
-- 创建名为 test_table 的数据表
DB:exec("CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)")
// 删除名为 test_table 的数据表(如果存在)
DB.exec("DROP TABLE IF EXISTS test_table");
// 创建名为 test_table 的数据表
DB.exec("CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)");
-- 删除名为 test_table 的数据表(如果存在)
DB\exec "DROP TABLE IF EXISTS test_table"
-- 创建名为 test_table 的数据表
DB\exec "CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)"
3.3 插入数据
使用 insert
方法可以将数据插入到表中:
- Lua
- Teal
- TypeScript
- YueScript
local success = DB:insert("test_table", {
{1, "Hello"},
{2, "World"}
})
print(success and "插入成功" or "插入失败")
local success = DB:insert("test_table", {
{1, "Hello"},
{2, "World"}
})
print(success and "插入成功" or "插入失败")
const success = DB.insert("test_table", [
[1, "Hello"],
[2, "World"]
]);
print(success ? "插入成功" : "插入失败");
success = DB\insert "test_table", [
[1, "Hello"],
[2, "World"]
]
print success and "插入成功" or "插入失败"
3.4 查询数据
使用 query
方法可以从表中查询数据:
- Lua
- Teal
- TypeScript
- YueScript
local results = DB:query("SELECT * FROM test_table")
for _, row in ipairs(results) do
print("ID:", row[1], "Value:", row[2])
end
local results = DB:query("SELECT * FROM test_table")
if not results is nil then
for _, row in ipairs(results) do
print("ID:", row[1], "Value:", row[2])
end
end
const results = DB.query("SELECT * FROM test_table");
if (results) {
for (const [id, value] of results) {
print("ID:", id, "Value:", value);
}
}
results = DB\query "SELECT * FROM test_table"
for [id, value] in *results
print "ID:", id, "Value:", value
3.5 更新和删除数据
可以使用 exec
方法执行更新和删除操作:
- Lua
- Teal
- TypeScript
- YueScript
-- 更新数据
local rowsAffected = DB:exec("UPDATE test_table SET value = ? WHERE id = ?", {"Hello Dora", 1})
print("更新了", rowsAffected, "行")
-- 删除数据
rowsAffected = DB:exec("DELETE FROM test_table WHERE id = ?", {2})
print("删除了", rowsAffected, "行")
-- 更新数据
local rowsAffected = DB:exec("UPDATE test_table SET value = ? WHERE id = ?", {"Hello Dora", 1})
print("更新了", rowsAffected, "行")
-- 删除数据
rowsAffected = DB:exec("DELETE FROM test_table WHERE id = ?", {2})
print("删除了", rowsAffected, "行")
// 更新数据
let rowsAffected = DB.exec("UPDATE test_table SET value = ? WHERE id = ?", ["Hello Dora", 1]);
print("更新了", rowsAffected, "行");
// 删除数据
rowsAffected = DB.exec("DELETE FROM test_table WHERE id = ?", [2]);
print("删除了", rowsAffected, "行");
-- 更新数据
rowsAffected = DB\exec "UPDATE test_table SET value = ? WHERE id = ?", ["Hello Dora", 1]
print "更新了", rowsAffected, "行"
-- 删除数据
rowsAffected = DB\exec "DELETE FROM test_table WHERE id = ?", [2]
print "删除了", rowsAffected, "行"
4. 事务处理
事务是一组要么全部执行并执行成功. 要么遇到错误就全部都不执行的操作。在 Dora SSR 中,可以使用 transaction
方法来执行事务:
- Lua
- Teal
- TypeScript
- YueScript
local sqlStatements = {
"INSERT INTO test_table (id, value) VALUES (3, 'Dora')",
"INSERT INTO test_table (id, value) VALUES (4, 'SSR')"
}
local transactionSuccess = DB:transaction(sqlStatements)
print(transactionSuccess and "事务成功" or "事务失败")
local sqlStatements = {
"INSERT INTO test_table (id, value) VALUES (3, 'Dora')",
"INSERT INTO test_table (id, value) VALUES (4, 'SSR')"
}
local transactionSuccess = DB:transaction(sqlStatements)
print("事务成功")
const sqlStatements = [
"INSERT INTO test_table (id, value) VALUES (3, 'Dora')",
"INSERT INTO test_table (id, value) VALUES (4, 'SSR')"
];
const transactionSuccess = DB.transaction(sqlStatements);
print(transactionSuccess ? "事务成功" : "事务失败");
sqlStatements = [
"INSERT INTO test_table (id, value) VALUES (3, 'Dora')",
"INSERT INTO test_table (id, value) VALUES (4, 'SSR')"
]
transactionSuccess = DB\transaction sqlStatements
print transactionSuccess and "事务 成功" or "事务失败"
5. 异步操作
为了不阻塞主线程,Dora SSR 提供了异步操作的方法,如 insertAsync
. queryAsync
和 execAsync
。这些方法可以在后台线程中执行数据库操作。
- Lua
- Teal
- TypeScript
- YueScript
thread(function()
-- 异步插入数据
DB:insertAsync("test_table", {
{5, "Async"},
{6, "Operation"}
})
-- 异步查询数据
local asyncResults = DB:queryAsync("SELECT * FROM test_table")
if asyncResults then
for _, row in ipairs(asyncResults) do
print("异步查询 - ID:", row[1], "Value:", row[2])
end
end
end)
thread(function()
-- 异步插入数据
DB:insertAsync("test_table", {
{5, "Async"},
{6, "Operation"}
})
-- 异步查询数据
local asyncResults = DB:queryAsync("SELECT * FROM test_table")
if not asyncResults is nil then
for _, row in ipairs(asyncResults) do
print("异步查询 - ID:", row[1], "Value:", row[2])
end
end
end)
thread(() => {
// 异步插入数据
DB.insertAsync("test_table", [
[5, "Async"],
[6, "Operation"]
]);
// 异步查询数据
const asyncResults = DB.queryAsync("SELECT * FROM test_table");
if (asyncResults) {
for (const [id, value] of asyncResults) {
print("异步查询 - ID:", id, "Value:", value);
}
}
});
thread ->
-- 异步插入数据
DB\insertAsync "test_table", [
[5, "Async"],
[6, "Operation"]
]
-- 异步查询数据
asyncResults = DB\queryAsync "SELECT * FROM test_table"
for [id, value] in *asyncResults
print "异步查询 - ID:", id, "Value:", value
6. 建立新的 Schema
在实际项目中,有时会需要建立一个新的 Schema 来分别存储管理游戏数据。而不是把数据都放在同一个默认库中。Schema 是一种数据库中的逻辑结构,可以看作是包含多个数据表的一个分组。可以使用 exec
方法来创建 Schema。
- Lua
- Teal
- TypeScript
- YueScript
-- 定义存储新的 Schema 数据的文件完整路径,必须是在引擎可写目录下
local schemaFile = Path(Content.writablePath, "game_data.db")
-- 创建 Schema,
-- 当数据库文件不存在时会自动创建,
-- 存在时会添加到当前数据库连接中
DB:exec("ATTACH DATABASE '" .. schemaFile .. "' AS game_data")
-- 在新的 Schema 中创建数据表并插入数据
DB:exec("CREATE TABLE game_data.player (id INTEGER PRIMARY KEY, name TEXT)")
DB:insert("game_data.player", {false, "Dora"})
-- 查询新的 Schema 中的表数据
DB:query("SELECT * FROM game_data.player")
-- 卸载 Schema,使得新的 Schema 数据不再可以访问
DB:exec("DETACH DATABASE game_data")
-- 定义存储新的 Schema 数据的文件完整路径,必须是在引擎可写目录下
local schemaFile = Path(Content.writablePath, "game_data.db")
-- 创建 Schema,
-- 当数据库文件不存在时会自动创建,
-- 存在时会添加到当前数据库连接中
DB:exec("ATTACH DATABASE '" .. schemaFile .. "' AS game_data")
-- 在新的 Schema 中创建数据表并插入数据
DB:exec("CREATE TABLE game_data.player (id INTEGER PRIMARY KEY, name TEXT)")
DB:insert("game_data.player", {false, "Dora"})
-- 查询新的 Schema 中的表数据
DB:query("SELECT * FROM game_data.player")
-- 卸载 Schema,使得新的 Schema 数据不再可以访问
DB:exec("DETACH DATABASE game_data")
// 定义存储新的 Schema 数据的文件完整路径,必须是在引擎可写目录下
const schemaFile = Path(Content.writablePath, "game_data.db");
// 创建 Schema,
// 当数据库文件不存在时会自动创建,
// 存在时会添加到当前数据库连接中
DB.exec(`ATTACH DATABASE '${schemaFile}' AS game_data`);
// 在新的 Schema 中创建数据表并插入数据
DB.exec("CREATE TABLE game_data.player (id INTEGER PRIMARY KEY, name TEXT)");
DB.insert("game_data.player", [false, "Dora"]);
// 查询新的 Schema 中的表数据
DB.query("SELECT * FROM game_data.player");
// 卸载 Schema,使得新的 Schema 数据不再可以访问
DB.exec("DETACH DATABASE game_data");
-- 定义存储新的 Schema 数据的文件完整路径,必须是在引擎可写目录下
schemaFile = Path(Content\writablePath, "game_data.db")
-- 创建 Schema,
-- 当数据库文件不存在时会自动创建,
-- 存在时会添加到当前数据库连接中
DB\exec "ATTACH DATABASE '#{schemaFile}' AS game_data"
-- 在新的 Schema 中创建数据表并插入数据
DB\exec "CREATE TABLE game_data.player (id INTEGER PRIMARY KEY, name TEXT)"
DB\insert "game_data.player", [false, "Dora"]
-- 查询新的 Schema 中的表数据
DB\query "SELECT * FROM game_data.player"
-- 卸载 Schema,使得新的 Schema 数据不再可以访问
DB\exec "DETACH DATABASE game_data"
Dora SSR 引擎提供的默认 Schema 库会存储在代码 Path(Content.writablePath, "dora.db")
所对应的文件中。在访问数据表时,如果没有在表名前加前缀,即表示访问的是默认 Schema 中的数据表。如果需要创建新的 Schema 库,并存储在一个独立的更容易进行迁移使用的数据库文件中,可以使用 ATTACH DATABASE
和 DETACH DATABASE
来进行操作。
7. 将 Excel 数据导入数据库
在游戏开发中,通常会使用 Excel 表格来管理游戏数据。当 Excel 中的数据量较大,做数据关联等复杂的查询操作时,使用数据库会更加高效。Dora SSR 引擎提供了便捷的将 Excel 数据导入数据库的功能,方便开发者进行数据管理。
7.1 前置条件
- 数据表结构与 Excel 表格结构一致:确保数据库中的表结构(列名和列类型)与 Excel 工作表中的数据列对应。
- Excel 文件格式:目前支持的 Excel 文件格式为
.xlsx
。
7.2 示例步骤
下面我们将通过一个示例,演示如何将 Excel 数据导入到数据库中。
创建数据库表
假设我们有一个 Excel 文件 data.xlsx
,其中包含一个工作表 Items
,记录了游戏道具的信息,包括道具 ID. 名称和描述。首先,我们需要在数据库中创建对应的表:
- Lua
- Teal
- TypeScript
- YueScript
DB:exec([[
CREATE TABLE IF NOT EXISTS Items (
id INTEGER PRIMARY KEY,
name TEXT,
description TEXT
)
]])
DB:exec([[
CREATE TABLE IF NOT EXISTS Items (
id INTEGER PRIMARY KEY,
name TEXT,
description TEXT
)
]])
DB.exec(`
CREATE TABLE IF NOT EXISTS Items (
id INTEGER PRIMARY KEY,
name TEXT,
description TEXT
)
`);
DB\exec [[
CREATE TABLE IF NOT EXISTS Items (
id INTEGER PRIMARY KEY,
name TEXT,
description TEXT
)
]]
准备 Excel 文件
确保您的 Excel 文件 data.xlsx
位于项目的可访问路径下,工作表 Items
的第一行为列名,对应数据库表的列名:
id | name | description |
---|---|---|
1 | Sword | Basic sword |
2 | Shield | Basic shield |
... | ... | ... |
使用 DB.insertAsync
导入数据
- Lua
- Teal
- TypeScript
- YueScript
thread(function()
local success = DB:insertAsync(
{"Items"},
"data.xlsx",
2
)
if success then
print("Excel 数据导入成功!")
else
print("Excel 数据导入失败!")
end
end)
thread(function()
local success = DB:insertAsync(
{"Items"},
"data.xlsx",
2
)
if success then
print("Excel 数据导入成功!")
else
print("Excel 数据导入失败!")
end
end)
thread(() => {
const success = DB.insertAsync(
["Items"],
"data.xlsx",
2
);
if (success) {
print("Excel 数据导入成功!");
} else {
print("Excel 数据导入失败!");
}
});
thread ->
success = DB\insertAsync(
["Items",]
"data.xlsx"
2
)
if success
print "Excel 数据导入成功!"
else
print "Excel 数据导入失败!"
验证导入结果
您可以查询数据库,验证数据是否成功导入:
- Lua
- Teal
- TypeScript
- YueScript
local items = DB:query("SELECT * FROM Items")
for _, item in ipairs(items) do
print("ID:", item[1], "名称:", item[2], "描述:", item[3])
end
local items = DB:query("SELECT * FROM Items")
if not items is nil then
for _, item in ipairs(items) do
print("ID:", item[1], "名称:", item[2], "描述:", item[3])
end
end